﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using PServiceBus.Core.Runtime.Extensions;
using System.Configuration;
using PServiceBus.Services.Gateway.Runtime;
using PServiceBus.Core.Logger;
using PServiceBus.Core.Runtime;
using System.Xml;
using PServiceBus.Core.Interface;
using Phoenix.ReflectionExtensions;
using PServiceBus.Serializer.Xml;
using PServiceBus.Gateway.Interfaces;
using PServiceBus.Core.Manager;
using System.Collections.Concurrent;
using System.IO;
using PServiceBus.Core.Runtime.Configuration;

namespace PServiceBus.WebSocket {
	public partial class Service : ServiceBase {
		static string _websocketName;
		static int _websocketPort;
		static string _websocketHost;
		static string _websocketType;
		static string _messageBusType;
		static bool _websocketSecured;

		static IWebSocketServer _websocket;
		private static string _cerfx;

		public Service() {
			InitializeComponent();
			ServiceName = _websocketName;
			Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
		}

		protected void Configure() {
			PServiceBus.Core.Runtime.SerializationBootstrapper.Configure();
			PServiceBus.EmailTransport.SerializationBootstrapper.Configure();
			PServiceBus.MSSQLTransport.SerializationBootstrapper.Configure();
			PServiceBus.WCF.NetTcpTransport.SerializationBootstrapper.Configure();
			PServiceBus.Transports.SerializationBootstrapper.Configure();
			PServiceBus.Services.Gateway.Runtime.SerializationBootstrapper.Configure();
		}

		protected override void OnStart(string[] args) {
			XmlSerializer.Build();
			DynamicBroker.Broker[_messageBusType].New();

			Configure();

			_websocket = DynamicBroker.Broker[_websocketType].New() as IWebSocketServer;
			_websocket.Cerfx = _cerfx;

			_websocket.OnMessage = (socket, message) => {
				var subscriberInfo = message.FromJson<WebSocketSubscriberInfo>();
				subscriberInfo.ClientIpAddress = socket.ClientIpAddress;

				var success = true;
				WebStreamManagementManager.Execute(webMgmt => success = webMgmt.Open(subscriberInfo.ClientToken, subscriberInfo.Headers));

				if (!success) {
					socket.Send("{\"esbst\": \"invalid\", \"esbrn\": \"could not start subscription\"}");
					return;
				}

				WebSocketHandler.AddWebSocketConnection(socket, subscriberInfo);
				WebSocketHandler.StreamMessages(subscriberInfo);
			};
			_websocket.OnError = (socket, error) => { };
			_websocket.OnClose = socket => {
				try {
					IMessageBus messageBus = null;
					string clientToken = null;
					Dictionary<string, string> headers = null;
					if (WebSocketHandler._connections.ContainsKey(socket.ID)) {
						var webSocketKey = string.Empty;
						WebSocketHandler._connections.TryRemove(socket.ID, out webSocketKey);
						if (!String.IsNullOrWhiteSpace(webSocketKey) &&
							WebSocketHandler._webSockets.ContainsKey(webSocketKey)) {
							ConcurrentDictionary<string, dynamic> handlers;
							WebSocketHandler._webSockets.TryRemove(webSocketKey, out socket);
							if (WebSocketHandler._handlers.TryRemove(webSocketKey, out handlers)) {
								foreach (var kv in handlers) {
									var handler = kv.Value;
									handler.Stop();
								}
							}
							if (WebSocketHandler._messageBuses.TryRemove(webSocketKey, out messageBus))
								messageBus.Dispose();
							if (WebSocketHandler._connectionTokens.TryRemove(webSocketKey, out clientToken)) {
								if (WebSocketHandler._connectionHeaders.TryRemove(webSocketKey, out headers)) {
									WebStreamManagementManager.Execute(webMgmt => webMgmt.Close(clientToken, headers));
								}
							}
						}
					}
				} catch (Exception ex) {
					ESBLogger.Log(ex);
				}
			};
			_websocket.Start(_websocketHost, _websocketPort, _websocketSecured);
			WebSocketConnectionManager.Start();
			FlashPolicyServer.Start();
		}

		static void LoadServiceInfo(string id) {
			var service = ConfigurationParser.Process("services.xml")
					.Select(x => x["service"] as Dictionary<string, object>)
					.Select(x => x.ToDictionary(kv => kv.Key, kv => kv.Value == null ? string.Empty : kv.Value.ToString()))
					.First(x => x["id"] == id);
			_websocketName = service["name"];
			_websocketPort = Convert.ToInt32(service["port"]);
			_websocketSecured = Convert.ToBoolean(service["secured"]);
			_websocketType = service["websocketType"];
			_websocketHost = service["host"];
			_messageBusType = service["messageBusType"];
			_cerfx = service["cerfx"];
			WebSocketConnectionSetting.KeepAlive = Convert.ToInt32(service["keepAlive"]);
			WebSocketHandler._esbEndpointAddress = service["esbEndpoint"];
			ShellHelper.NetShAddTCPFirewall(_websocketName, _websocketPort);
		}

		static void Main(string[] args) {
			if (args.Length < 1)
				throw new ArgumentException("WebSocket Service Require service name to be passed to the command line");
			LoadServiceInfo(String.Join(" ", args));
			ServiceBase[] ServicesToRun;
			ServicesToRun = new ServiceBase[] 
			{ 
				new Service() 
			};
			ServiceBase.Run(ServicesToRun);
		}

		protected override void OnStop() {
			try {
				if (_websocket != null) MethodHelper.Try(_websocket.Close);
				WebSocketHandler.CloseMessageBus();
				WebSocketConnectionManager.Stop();
				FlashPolicyServer.Stop();
			} catch { }
			ShellHelper.NetShRemoveUrlAcl(_websocketPort);
		}
	}
}
